package com.liuguang.realgateway.config;

import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import com.liuguang.realgateway.handler.MyBlockRequestHandler;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.result.view.ViewResolver;

import javax.annotation.PostConstruct;
import java.util.*;

/**
 * @ClassName: MySentinelConfig
 * @Description：sentinel 配置
 * @Author: wanghr
 * Date: 4:06 PM 2020/11/2
 * Version: 1.0
 */
@Configuration
public class MySentinelConfig {
    /**
     *  注册限流异常拦截异常处理器，后序是不是可以自己创建处理器来处理限流异常？
     *  没必要自己实现异常处理器，来拦截异常并且判断异常是不是 blockException ，
     *  是的话就使用 GatewayCallbackManager 中设置的handler 处理异常，不是的话使用 Mono.error(e)；继续抛出异常
     *  这些在sentinel源码中已经做过了，只需要在容器初始化的时候向 GatewayCallbackManager 中设置自定义的 BlockRequestHandler接口实现类即可
     */
//    @Bean
//    @Order(Ordered.HIGHEST_PRECEDENCE)
//    public MySentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
//        // Register the block exception handler for Spring Cloud Gateway.
//        return new MySentinelGatewayBlockExceptionHandler();
//    }

    /**
     * 注册流量监控网关过滤器。
     */
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public GatewayFilter sentinelGatewayFilter() {
        return new SentinelGatewayFilter();
    }

    /**
     * 在类对象初始化后执行
     */
    @PostConstruct
    public void init() {
        //初始化GatewayCallbackManager
        initGatewayCallbackManager();

        //使用 nacos 进行动态配置
        //初始化gatewayRules   todo：这些是不是可以通过Bean方式注入？待验证
//        initGatewayRules();
        //初始化熔断降级的设置
        initDegradeRuleManager();
    }

    private void initDegradeRuleManager() {
        ArrayList<DegradeRule> degradeRules = new ArrayList<>();
        DegradeRule rule = new DegradeRule("gateway");
        //设置熔断类型，异常出现类型
        rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);
        //出现两次异常后熔断服务
        rule.setCount(1);
        //两次请求内即使出现异常也不会熔断
        rule.setMinRequestAmount(2);
        //熔断的时间 单位默认为秒
        rule.setTimeWindow(10);
        DegradeRuleManager.loadRules(degradeRules);
    }

    private void initGatewayCallbackManager() {
        GatewayCallbackManager.setBlockHandler(new MyBlockRequestHandler());
    }

    private void initGatewayRules() {
        System.out.println("初始化 sentinel 绑定资源");
        Set<GatewayFlowRule> rules = new HashSet<>();

//        规则的名和限流阈值是自己要设定的，其他一些属性有默认值
        //绑定 gateway 服务的限流， 资源名是服务的名称，但是集群下，设置的阈值，应该是所有服务器的总和吗？如何保证每个服务器不超？
//        在每个服务中再次进行限流吗？还是在网关的负载均衡就能保证？
//        在不是集群下，当route 的 uri 是路径的话，创建规则的名，如何填写？
        GatewayFlowRule rule = new GatewayFlowRule("gateway22");
//        设置阈值为1
        rule.setCount(1);
//        以下配置是默认的配置：
//        限流阈值类型：0：最大并发线程数；1：QPS 每秒最大查询率，默认是：1
        rule.setGrade(0);
        rules.add(rule);


        GatewayRuleManager.loadRules(rules);
    }
}
